﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using PTools.SharePointEntity.Exceptions;
using PTools.SharePointEntity.Attributes;
using System.Reflection;

namespace PTools.SharePointEntity
{
    /// <summary>
    /// This base class needs to be implemented as base class for all the entities
    /// </summary>
    public abstract class EntityBase
    {
        #region Private Members
        private SPListItem _listItem;
        #endregion

        #region Constructor
        #endregion

        #region Properties
        private int _itemID;
        /// <summary>
        /// The SPListItemID of the Entity
        /// </summary>
        public int ItemID
        {
            get
            {
                return _itemID;
            }
        }
        /// <summary>
        /// Returns the web of the site
        /// </summary>
        private SPWeb GetWebInternal
        {
            get
            {
                // There is already a SPWeb object
                // Use this one
                return GetWeb();
            }
        }
        #endregion

        #region Constuctor
        #endregion

        #region Must Implement
        /// <summary>
        /// Return the SPWeb object where the item should be created
        /// If NULL GetWebURL will be used.
        /// </summary>
        /// <returns></returns>
        public abstract SPWeb GetWeb();
        #endregion

        #region Methods
        /// <summary>
        /// Create a new instance of the entity
        /// </summary>
        /// <returns>True if succesfull</returns>
        public bool CreateNew()
        {
            // Get the List
            SPList list = GetSPList();

            // Create the item
            _listItem = list.Items.Add();

            // Set the Item ID
            _itemID = _listItem.ID;

            return true;
        }

        /// <summary>
        /// This will load the Entity from SharePoint
        /// </summary>
        /// <param name="item">The SPListItem that should represent this</param>
        /// <returns>True if sucessfull</returns>
        public bool LoadEntity(SPListItem item)
        {
            if (item == null)
                throw new ArgumentNullException("item");

            // Set the reference
            _listItem = item;

            // Load the properties
            LoadProperties();

            return true;
        }

        /// <summary>
        /// This will load the Entity from SharePoint
        /// </summary>
        /// <param name="query">The SPQuery that should return the item needed. 
        /// If there is no item for the query an EntityNotExistException will be thrown.
        /// </param>
        /// <returns>True if sucessfull</returns>
        public bool LoadEntity(SPQuery query)
        {
            // Default argument check
            if (query == null)
                throw new ArgumentNullException("query");

            // Get the list
            SPList list = GetSPList();

            // Launch the query
            SPListItemCollection results = list.GetItems(query);

            // Check if there are items
            if(results.Count == 0)
                throw new EntityNotExistException();

            // Set the reference
            _listItem = results[0];

            // Load the properties
            LoadProperties();

            return true;
        }

        /// <summary>
        /// This will load the Entity from SharePoint
        /// </summary>
        /// <param name="id">The ID of the Entity to load</param>
        /// <returns>True if sucessfull</returns>
        public bool LoadEntity(int id)
        {
            if (id == null || id < 0)
                throw new ArgumentNullException("id");

            // Set the Item ID
            _itemID = id;

            // Get the List
            SPList list = GetSPList();

            // Get the Item
            try
            {
                _listItem = list.GetItemById(id);
            }
            catch (ArgumentException argEx)
            {
                throw new EntityNotExistException();
            }

            // Load the properties
            LoadProperties();

            return true;
        }

        /// <summary>
        /// Updates this instance of the entity
        /// </summary>
        /// <returns>True if succesfull</returns>
        public bool Update()
        {
            // Check if the entity exists
            if (_listItem == null)
                throw new EntityNotExistException();

            SaveProperties();

            _listItem.Update();

            return true;
        }

        /// <summary>
        /// Updates or Creates the item if it does not exist
        /// </summary>
        /// <param name="forceCreate">True to create if not exist</param>
        /// <returns></returns>
        public bool Update(bool forceCreate)
        {
            // Check if the entity exists
            if (_listItem == null)
            {
                // Only if force create 
                if (forceCreate)
                {
                    _listItem = GetSPList().Items.Add();
                }
                else
                {
                    throw new EntityNotExistException();
                }
            }

            SaveProperties();

            _listItem.Update();

            return true;
        }

        /// <summary>
        /// Delets this instance of the entity
        /// </summary>
        /// <returns>True if succesfull</returns>
        public bool Delete()
        {
            // Check if the entity exists
            if (_listItem == null)
                throw new EntityNotExistException();

            // Send to the recycle bin
            _listItem.Recycle();

            return true;
        }
        #endregion

        #region Helper Methods
        /// <summary>
        /// Returns the SPList Corresponding to the Entity
        /// </summary>
        /// <returns></returns>
        private SPList GetSPList()
        {
            // Get the Listname of the item
            object[] classAttributes = this.GetType().GetCustomAttributes(typeof(ListAttribute), true);

            // Check if it was implemented
            if (classAttributes.Length == 0)
                throw new AttributeNotImplementedException("ListAttribute");

            // Get the attribute
            ListAttribute listAttribute = classAttributes[0] as ListAttribute;

            // Get the listname out of it
            string strListName = listAttribute.ListName;

            // Get the List
            SPList list = null;

            // Get the list
            try
            {
                // Get the List Where it is located
                list = GetWebInternal.Lists[strListName];
            }
            catch
            {
                throw new ListNotFoundException(strListName);
            }
            return list;
        }

        /// <summary>
        /// This method will Load the properies back from SharePoint
        /// </summary>
        private void LoadProperties()
        {
            // Get the properties
            PropertyInfo[] props = this.GetType().GetProperties();

            // Loop all the fields and update them
            foreach (PropertyInfo pi in props)
            {
                // Get the attributes
                object[] attributes = pi.GetCustomAttributes(typeof(FieldAttribute), false);

                // Only do our logic if our attribute is implemented
                if (attributes.Length > 0)
                {
                    // Get the FieldAttribute
                    FieldAttribute fieldAttribute = attributes[0] as FieldAttribute;

                    // Get the FieldName
                    string strFieldName = fieldAttribute.FieldName;

                    // If item does not contain the field, throw an error
                    if (!_listItem.Fields.ContainsField(strFieldName))
                        throw new FieldNotFoundException(strFieldName, GetSPList().Title);

                    // Get the value
                    object value = _listItem[strFieldName];

                    // If it is a SPUser Field
                    if (pi.PropertyType == typeof(SPUser))
                        value = new SPFieldUserValue(GetWebInternal, value.ToString()).User;

                    // If it is a SPUser Field
                    if (pi.PropertyType == typeof(SPFieldUserValue))
                        value = new SPFieldUserValue(GetWebInternal, value.ToString()).User;

                    // If it is a multi user field
                    if (pi.PropertyType == typeof(SPFieldUserValueCollection))
                        value = new SPFieldUserValueCollection(GetWebInternal, value.ToString());

                    // if it is a URL Field
                    if (pi.PropertyType == typeof(SPFieldUrlValue))
                        value = new SPFieldUrlValue(value.ToString());

                    // if it is a string field
                    if (pi.PropertyType == typeof(string))
                        value = value.ToString();

                    // If it is a lookup value
                    if (pi.PropertyType == typeof(SPFieldLookupValue))
                        value = new SPFieldLookupValue(value.ToString());

                    // If it is a lookup value collection
                    if (pi.PropertyType == typeof(SPFieldLookupValueCollection))
                        value = new SPFieldLookupValueCollection(value.ToString());

                    // If it is a multi choice value
                    if (pi.PropertyType == typeof(SPFieldMultiChoiceValue))
                        value = new SPFieldMultiChoiceValue(value.ToString());

                    // If it is a multi column value
                    if (pi.PropertyType == typeof(SPFieldMultiColumnValue))
                        value = new SPFieldMultiColumnValue(value.ToString());


                    // Set the value
                    pi.SetValue(this, value, null);
                }
            }
        }

        /// <summary>
        /// This method will save the properies back to SharePoint
        /// </summary>
        private void SaveProperties()
        {
            // Get the properties
            PropertyInfo[] props = this.GetType().GetProperties();

            // Loop all the fields and update them
            foreach (PropertyInfo pi in props)
            {
                // Get the attributes
                object[] attributes = pi.GetCustomAttributes(typeof(FieldAttribute), false);

                // Only do our logic if our attribute is implemented
                if (attributes.Length > 0)
                {
                    // Get the FieldAttribute
                    FieldAttribute fieldAttribute = attributes[0] as FieldAttribute;

                    // Get the FieldName
                    string strFieldName = fieldAttribute.FieldName;

                    // If item does not contain the field, throw an error
                    if (!_listItem.Fields.ContainsField(strFieldName))
                        throw new FieldNotFoundException(strFieldName, GetSPList().Title);

                    // Get the value of the property
                    object value = pi.GetValue(this, null);

                    // Only set if there is a value
                    if (value != null && value.GetType() != null)
                    {
                        // Determine if the property should be set
                        bool shouldSet = true;

                        // Empty DateTimes shouldn't be set
                        if (value.GetType() == typeof(DateTime) && DateTime.Parse(value.ToString()) == DateTime.MinValue)
                            shouldSet = false;

                        // Empty Strings shouldn't be set
                        if (value.GetType() == typeof(string) && string.IsNullOrEmpty(value.ToString()))
                            shouldSet = false;

                        // Set it
                        if (shouldSet)
                            // Set both equal on the listItem
                            _listItem[strFieldName] = value;
                    }
                }
            }
        }
        #endregion
    }
}
